/**
 * Copyright © 2014 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.rayson.tool.apt;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;

/**
 * Annotation processor used to process Rayson Protocol service class.
 * 
 * @author creativor
 */
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({ AnnotationProcessor.TARGET_ANNOTATION_NAME })
public class AnnotationProcessor extends AbstractProcessor {
	protected final static String TARGET_ANNOTATION_NAME = "org.rayson.api.annotation.Service";
	private final static String PROTOCOL_INTERFACE_NAME = "org.rayson.api.Protocol";

	private static String getErrorInfoFromException(final Throwable e) {
		try {
			final StringWriter sw = new StringWriter();
			final PrintWriter pw = new PrintWriter(sw);
			e.printStackTrace(pw);
			return sw.toString();
		} catch (final Exception e2) {
			return "bad getErrorInfoFromException";
		}
	}

	/**
	 * @param protocolType
	 * @throws IOException
	 */
	private void generateMetaData(final TypeElement protocolType)
			throws IOException {
		final ProtocolMetaData metaData = new ProtocolMetaData(protocolType,
				this.processingEnv);
		final String packageName = metaData.getPackageName();

		final CharSequence metaClassName = metaData.getClassSimpleName();
		final JavaFileObject jfo = this.processingEnv.getFiler()
				.createSourceFile(packageName + "." + metaClassName);
		final Writer writer = jfo.openWriter();

		writer.append(metaData.generateJavaCode());
		writer.close();
	}

	@Override
	public synchronized void init(final ProcessingEnvironment processingEnv) {
		processingEnv.getMessager().printMessage(Kind.NOTE,
				"Init APT " + this.getClass().getName() + " ...");
		super.init(processingEnv);

	}

	@Override
	public boolean process(final Set<? extends TypeElement> annotations,
			final RoundEnvironment roundEnv) {
		Set<? extends Element> annotatedTypes;
		for (final TypeElement typeElement : annotations) {
			annotatedTypes = roundEnv.getElementsAnnotatedWith(typeElement);
			for (final Element element : annotatedTypes) {
				this.processingEnv.getMessager().printMessage(Kind.NOTE,
						"Found service annotated element -> " + element);
				processProtocol(element);
			}
		}

		return true;
	}

	/**
	 * @param element
	 */
	private void processProtocol(final Element element) {
		final TypeMirror elementType = element.asType();
		final ElementKind kind = element.getKind();
		final TypeKind typeKind = elementType.getKind();
		if (typeKind != TypeKind.DECLARED || !kind.isInterface()) {
			this.processingEnv.getMessager().printMessage(Kind.WARNING,
					"The Service annotated type should be an interface");
			return;
		}

		final TypeElement trueElem = (TypeElement) element;

		// Validate Protocol interface.
		final List<? extends TypeMirror> interfaces = trueElem.getInterfaces();
		boolean isProtocol = false;
		for (final TypeMirror interfaceType : interfaces) {
			if (interfaceType.toString().equals(PROTOCOL_INTERFACE_NAME)) {
				isProtocol = true;
				break;
			}
		}
		if (!isProtocol) {
			this.processingEnv.getMessager().printMessage(
					Kind.WARNING,
					"The Service annotated type should be an child class of "
							+ PROTOCOL_INTERFACE_NAME);
			return;

		}

		// Do generate meta data of Protocol Interface.
		try {
			generateMetaData(trueElem);
		} catch (final Throwable e) {
			this.processingEnv.getMessager().printMessage(
					Kind.WARNING,
					"Generate meta data  failed :\n"
							+ getErrorInfoFromException(e), trueElem);
		}
	}
}